/*-------------------------------------------------------------------------
 *
 * nodeNestloop.c
 *	  routines to support nest-loop joins
 *
 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeActrlJoin.c
 *
 *-------------------------------------------------------------------------
 */
/*
 *	 INTERFACE ROUTINES
 *		ExecActrlJoin	 - process a actrl join of two plans
 *		ExecInitActrlJoin - initialize the join
 *		ExecEndActrlJoin  - shut down the join
 */

#include "postgres.h"
#include "executor/execdebug.h"
#include "executor/nodeNestloop.h"
#include "executor/nodeHashjoin.h"
#include "executor/nodeActrljoin.h"
#include "miscadmin.h"
#include "access/htup_details.h"
#include "access/parallel.h"
#include "executor/executor.h"
#include "executor/hashjoin.h"
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
#include "miscadmin.h"
#include "utils/sharedtuplestore.h"
#include "utils/wait_event.h"

#define change_threshold 1000


/* ----------------------------------------------------------------
 *		ExecActrlJoin(node)
 *
 * old comments
 *		Returns the tuple joined from inner and outer tuples which
 *		satisfies the qualification clause.
 *
 *		It scans the inner relation to join with current outer tuple.
 *
 *		If none is found, next tuple from the outer relation is retrieved
 *		and the inner relation is scanned from the beginning again to join
 *		with the outer tuple.
 *
 *		NULL is returned if all the remaining outer tuples are tried and
 *		all fail to join with the inner tuples.
 *
 *		NULL is also returned if there is no tuple from inner relation.
 *
 *		Conditions:
 *		  -- outerTuple contains current tuple from outer relation and
 *			 the right son(inner relation) maintains "cursor" at the tuple
 *			 returned previously.
 *				This is achieved by maintaining a scan position on the outer
 *				relation.
 *
 *		Initial States:
 *		  -- the outer child and the inner child
 *			   are prepared to return the first tuple.
 * ----------------------------------------------------------------
 */
static TupleTableSlot *
ExecActrlJoin(PlanState *pstate)
{
	ActrlJoinState *node = castNode(ActrlJoinState, pstate);
	//node -> curr_using_NestLoop = true;
	//do change
	if(node->true_cards > change_threshold){
		//at this time,Actrl can exec HJ
		if(node->is_switching_moment){
			node->curr_using_NestLoop = false;
		}
		//Actrl should exec more NL so go!!!
	}
	TupleTableSlot*  tuple_from_actrl = NULL;
	
	if(node->curr_using_NestLoop){
		tuple_from_actrl = ExecActrlJoinInNLMode(pstate);
		if(TupIsNull(tuple_from_actrl) && node->is_switching_moment){
			tuple_from_actrl = ExecActrlJoinInHJMode(pstate);
		}
	}
	else{
		tuple_from_actrl = ExecActrlJoinInHJMode(pstate);
	}	
	if(TupIsNull(tuple_from_actrl)){
		ereport(LOG,
			(errmsg("true cards in actrljoin node are:%lf", node->true_cards)));
	}
	else{
		node->true_cards++;
		}
	return tuple_from_actrl;
}



/* ----------------------------------------------------------------
 *		ExecInitActrlJoin
 * ----------------------------------------------------------------
 */
ActrlJoinState *
ExecInitActrlJoin(ActrlJoin *node, EState *estate, int eflags)
{	
	//first establish actrlstate
	ActrlJoinState* actrlstate = makeNode(ActrlJoinState);
	//then establish hjstate
	HashJoinState* hjstate = ExecInitHashJoin((HashJoin*)(node), estate, eflags);
	NestLoopState* nlstate = ExecInitNestLoopFromActrlJoin(node, estate, eflags);
	nlstate->js.ps.lefttree = outerPlanState(hjstate);
	nlstate->js.ps.righttree = skiphashinnerPlanState(hjstate);
	//then memcpy
	memcpy(actrlstate, hjstate, sizeof(HashJoinState));
	pfree(hjstate);
	//then follow the same behaviour
	actrlstate->hjstate.js.ps.plan = (Plan *) node;
	actrlstate->hjstate.js.ps.state = estate;
	actrlstate->hjstate.js.ps.ExecProcNode = ExecActrlJoin;
	actrlstate->hjstate.js.ps.type = T_ActrlJoinState;
	actrlstate->nlstate = nlstate;
	//then care about NestLoop Behaviour
	//do some asserts
	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
	actrlstate->true_cards = 0;
	actrlstate->need_change = false;
	actrlstate->curr_using_NestLoop = true;
	actrlstate->is_switching_moment = false;
	//then we just return
	return actrlstate;
}


/* ----------------------------------------------------------------
 *		ExecInitNestLoopFromActrlJoin
 * ----------------------------------------------------------------
 */
NestLoopState *
ExecInitNestLoopFromActrlJoin(ActrlJoin *node, EState *estate, int eflags)
{	
	NestLoopState *nlstate;

	/* check for unsupported flags */
	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));

	/*
	 * create state structure
	 */
	nlstate = makeNode(NestLoopState);
	nlstate->js.ps.plan = (Plan *) node;
	nlstate->js.ps.state = estate;


	/*
	 * Miscellaneous initialization
	 *
	 * create expression context for node
	 */
	ExecAssignExprContext(estate, &nlstate->js.ps);

	/*
	 * Initialize result slot, type and projection.
	 */
	ExecInitResultTupleSlotTL(&nlstate->js.ps, &TTSOpsVirtual);
	ExecAssignProjectionInfo(&nlstate->js.ps, NULL);

	/*
	 * initialize child expressions
	 */
	nlstate->js.ps.qual =
		ExecInitQual(node->hashjoin.join.plan.qual, (PlanState *) nlstate);
	nlstate->js.jointype = node->hashjoin.join.jointype;
	nlstate->js.joinqual =
		ExecInitQual(node->hashjoin.join.joinqual, (PlanState *) nlstate);


	/*
	 * detect whether we need only consider the first matching inner tuple
	 */
	nlstate->js.single_match = (node->hashjoin.join.inner_unique ||
								node->hashjoin.join.jointype == JOIN_SEMI);

	/* set up null tuples for outer joins, if needed */
	switch (node->hashjoin.join.jointype)
	{
		case JOIN_INNER:
		case JOIN_SEMI:
			break;
		case JOIN_LEFT:
		case JOIN_ANTI:
			nlstate->nl_NullInnerTupleSlot =
				ExecInitNullTupleSlot(estate,
									  ExecGetResultType(innerPlanState(nlstate)),
									  &TTSOpsVirtual);
			break;
		default:
			elog(ERROR, "unrecognized join type: %d",
				 (int) node->hashjoin.join.jointype);
	}

	/*
	 * finally, wipe the current outer tuple clean.
	 */
	nlstate->nl_NeedNewOuter = true;
	nlstate->nl_MatchedOuter = false;

	return nlstate;
}




/* ----------------------------------------------------------------
 *		ExecEndActrlJoin
 *      when end, the cardinality is known !!!
 *		closes down scans and frees allocated storage
 * ----------------------------------------------------------------
 */
void
ExecEndActrlJoin(ActrlJoinState *node)
{
	//because there's no hashtable!!!
	if(node->curr_using_NestLoop){
		NL1_printf("ExecEndNestLoop: %s\n",
			   "ending node processing");
			/*
	 		* close down subplans
	 		*/
		ExecEndNode(outerPlanState(node));
		ExecEndNode(innerPlanState(node));

		NL1_printf("ExecEndNestLoop: %s\n",
			   	"node processing ended");
	}
	else{
		ExecEndHashJoin((HashJoinState*)node);
	}
}




/* ----------------------------------------------------------------
 *      
 *		ExecReScanActrlJoin
 *      the logic is very simple
 *      when rescan, the cardinality is known !!!
 * 		
 *
 * ----------------------------------------------------------------
 */
void
ExecReScanActrlJoin(ActrlJoinState *node)
{
	if(node->curr_using_NestLoop){
		PlanState  *outerPlan = outerPlanState(node);
		/*
	 	* If outerPlan->chgParam is not null then plan will be automatically
	 	* re-scanned by first ExecProcNode.
	 	*/
		if (outerPlan->chgParam == NULL)
			ExecReScan(outerPlan);

		/*
		 * innerPlan is re-scanned for each new outer tuple and MUST NOT be
	 	* re-scanned from here or you'll get troubles from inner index scans when
	 	* outer Vars are used as run-time keys...
	 	*/
		node->nlstate->nl_NeedNewOuter = true;
		node->nlstate->nl_MatchedOuter = false;
	}
	else{
		ExecReScanHashJoin((HashJoinState*)(node));
	}
}

// static TupleTableSlot*
// ExecActrlJoinInNLMode{
// 				NestLoop   *nl;
// 			PlanState  *innerPlan;
// 			PlanState  *outerPlan;
// 			TupleTableSlot *outerTupleSlot;
// 			TupleTableSlot *innerTupleSlot;
// 			ExprState  *joinqual;
// 			ExprState  *otherqual;
// 			ExprContext *econtext;
// 			ListCell   *lc;
// 			CHECK_FOR_INTERRUPTS();
		
// 			joinqual = node->nlstate->js.joinqual;
// 			otherqual = node->nlstate->js.ps.qual;
// 			outerPlan = outerPlanState(node);
// 			innerPlan = skiphashinnerPlanState(node);
// 			econtext = node->nlstate->js.ps.ps_ExprContext;

// 			/*
// 		 	* Reset per-tuple memory context to free any expression evaluation
// 	 		* storage allocated in the previous tuple cycle.
// 			 */
// 			ResetExprContext(econtext);

// 			/*
// 	 		* Ok, everything is setup for the join so now loop until we return a
// 	 		* qualifying join tuple.
// 			 */

// 			for (;;)
// 			{
// 				/*
// 		 		* If we don't have an outer tuple, get the next one and reset the
// 		 		* inner scan.
// 		 		*/
// 				if (node->nl_NeedNewOuter)
// 				{
// 					ENL1_printf("getting new outer tuple");
// 					outerTupleSlot = ExecProcNode(outerPlan);

// 					/*
// 			 		* if there are no more outer tuples, then the join is complete..
// 			 		*/
// 					if (TupIsNull(outerTupleSlot))
// 					{
// 						ENL1_printf("no outer tuple, ending join");
// 						return NULL;
// 					}

// 					ENL1_printf("saving new outer tuple information");
// 					econtext->ecxt_outertuple = outerTupleSlot;
// 					node->nl_NeedNewOuter = false;
// 					node->nl_MatchedOuter = false;

// 					/*
// 					 * now rescan the inner plan
// 			 		*/
// 					ENL1_printf("rescanning inner plan");
// 					ExecReScan(innerPlan);
// 				}

// 				/*
// 		 		* we have an outerTuple, try to get the next inner tuple.
// 				 */
// 				ENL1_printf("getting new inner tuple");

// 				innerTupleSlot = ExecProcNode(innerPlan);
// 				econtext->ecxt_innertuple = innerTupleSlot;

// 				if (TupIsNull(innerTupleSlot))
// 				{
// 					ENL1_printf("no inner tuple, need new outer tuple");

// 					node->nl_NeedNewOuter = true;

// 					if (!node->nl_MatchedOuter &&
// 						(node->hjstate.js.jointype == JOIN_LEFT ||
// 						 node->hjstate.js.jointype == JOIN_ANTI))
// 					{
// 						/*
// 						 * We are doing an outer join and there were no join matches
// 				 		* for this outer tuple.  Generate a fake join tuple with
// 				 		* nulls for the inner tuple, and return it if it passes the
// 						 * non-join quals.
// 						 */
// 					econtext->ecxt_innertuple = node->nl_NullInnerTupleSlot;

// 					ENL1_printf("testing qualification for outer-join tuple");

// 						if (otherqual == NULL || ExecQual(otherqual, econtext))
// 						{
// 							/*
// 							 * qualification was satisfied so we project and return
// 					 		* the slot containing the result tuple using
// 					 		* ExecProject().
// 							 */
// 						ENL1_printf("qualification succeeded, projecting tuple");

// 						return ExecProject(node->hjstate.js.ps.ps_ProjInfo);
// 					}
// 					else
// 						InstrCountFiltered2(node, 1);
// 				}

// 				/*
// 			 	* Otherwise just return to top of loop for a new outer tuple.
// 			 	*/
// 				continue;
// 			}

// 				/*
// 		 		* at this point we have a new pair of inner and outer tuples so we
// 		 		* test the inner and outer tuples to see if they satisfy the node's
// 				 * qualification.
// 		 		*
// 		 		* Only the joinquals determine MatchedOuter status, but all quals
// 		 		* must pass to actually return the tuple.
// 		 		*/
// 				ENL1_printf("testing qualification");

// 				if (ExecQual(joinqual, econtext))
// 				{
// 					node->nl_MatchedOuter = true;

// 					/* In an antijoin, we never return a matched tuple */
// 					if (node->hjstate.js.jointype == JOIN_ANTI)
// 					{
// 						node->nl_NeedNewOuter = true;
// 						continue;		/* return to top of loop */
// 					}

// 					/*
// 					 * If we only need to join to the first matching inner tuple, then
// 			 		* consider returning this one, but after that continue with next
// 			 		* outer tuple.
// 			 		*/
// 					if (node->hjstate.js.single_match)
// 					node->nl_NeedNewOuter = true;

// 					if (otherqual == NULL || ExecQual(otherqual, econtext))
// 				{
// 						/*
// 				 		* qualification was satisfied so we project and return the
// 				 		* slot containing the result tuple using ExecProject().
// 				 		*/
// 						ENL1_printf("qualification succeeded, projecting tuple");

// 						return ExecProject(node->hjstate.js.ps.ps_ProjInfo);
// 				}
// 					else
// 						InstrCountFiltered2(node, 1);
// 				}
// 				else
// 					InstrCountFiltered1(node, 1);

// 				ResetExprContext(econtext);
// 	}

// }
TupleTableSlot*
ExecActrlJoinInNLMode(PlanState *pstate){
	ActrlJoinState *init_node = castNode(ActrlJoinState, pstate);
	NestLoopState *node = init_node -> nlstate;
	PlanState  *innerPlan;
	PlanState  *outerPlan;
	TupleTableSlot *outerTupleSlot;
	TupleTableSlot *innerTupleSlot;
	ExprState  *joinqual;
	ExprState  *otherqual;
	ExprContext *econtext;
	ListCell   *lc;

	CHECK_FOR_INTERRUPTS();

	joinqual = node->js.joinqual;
	otherqual = node->js.ps.qual;
	outerPlan = outerPlanState(init_node);
	innerPlan = skiphashinnerPlanState(init_node);
	econtext = node->js.ps.ps_ExprContext;

	/*
	 * Reset per-tuple memory context to free any expression evaluation
	 * storage allocated in the previous tuple cycle.
	 */
	ResetExprContext(econtext);

	/*
	 * Ok, everything is setup for the join so now loop until we return a
	 * qualifying join tuple.
	 */
	ENL1_printf("entering main loop");

	for (;;)
	{
		/*
		 * If we don't have an outer tuple, get the next one and reset the
		 * inner scan.
		 */
		if (node->nl_NeedNewOuter)
		{
			ENL1_printf("getting new outer tuple");
			outerTupleSlot = ExecProcNode(outerPlan);

			/*
			 * if there are no more outer tuples, then the join is complete..
			 */
			if (TupIsNull(outerTupleSlot))
			{
				ENL1_printf("no outer tuple, ending join");
				return NULL;
			}

			ENL1_printf("saving new outer tuple information");
			econtext->ecxt_outertuple = outerTupleSlot;
			node->nl_NeedNewOuter = false;
			node->nl_MatchedOuter = false;

			/*
			 * now rescan the inner plan
			 */
			ENL1_printf("rescanning inner plan");
			ExecReScan(innerPlan);
		}

		/*
		 * we have an outerTuple, try to get the next inner tuple.
		 */

		innerTupleSlot = ExecProcNode(innerPlan);
		econtext->ecxt_innertuple = innerTupleSlot;

		if (TupIsNull(innerTupleSlot))
		{
			TupleTableSlot*  tuple_at_switching_moment = NULL;
			bool is_need_special_handle = false;
			node->nl_NeedNewOuter = true;
			if(pstate->type == T_ActrlJoinState){
				ActrlJoinState* actrljoinstate = castNode(ActrlJoinState, pstate);
				if(actrljoinstate -> true_cards > change_threshold){
					actrljoinstate -> is_switching_moment = true;
					is_need_special_handle = true;
				}
			}

			if (!node->nl_MatchedOuter &&
				(node->js.jointype == JOIN_LEFT ||
				 node->js.jointype == JOIN_ANTI))
			{
				/*
				 * We are doing an outer join and there were no join matches
				 * for this outer tuple.  Generate a fake join tuple with
				 * nulls for the inner tuple, and return it if it passes the
				 * non-join quals.
				 */
				econtext->ecxt_innertuple = node->nl_NullInnerTupleSlot;


				if (otherqual == NULL || ExecQual(otherqual, econtext))
				{
					/*
					 * qualification was satisfied so we project and return
					 * the slot containing the result tuple using
					 * ExecProject().
					 */

					tuple_at_switching_moment = ExecProject(node->js.ps.ps_ProjInfo);
					if(is_need_special_handle){
						ExecReScan(innerPlan);						
					}
					return tuple_at_switching_moment;
				}
				else
					InstrCountFiltered2(node, 1);
			}
			if(is_need_special_handle && tuple_at_switching_moment == NULL){
				ExecReScan(innerPlan);
				return NULL;
			}
			/*
			 * Otherwise just return to top of loop for a new outer tuple.
			 */
			continue;
		}

		/*
		 * at this point we have a new pair of inner and outer tuples so we
		 * test the inner and outer tuples to see if they satisfy the node's
		 * qualification.
		 *
		 * Only the joinquals determine MatchedOuter status, but all quals
		 * must pass to actually return the tuple.
		 */

		if (ExecQual(joinqual, econtext))
		{
			node->nl_MatchedOuter = true;

			/* In an antijoin, we never return a matched tuple */
			if (node->js.jointype == JOIN_ANTI)
			{
				node->nl_NeedNewOuter = true;
				continue;		/* return to top of loop */
			}

			/*
			 * If we only need to join to the first matching inner tuple, then
			 * consider returning this one, but after that continue with next
			 * outer tuple.
			 */
			if (node->js.single_match)
				node->nl_NeedNewOuter = true;

			if (otherqual == NULL || ExecQual(otherqual, econtext))
			{
				/*
				 * qualification was satisfied so we project and return the
				 * slot containing the result tuple using ExecProject().
				 */
				ENL1_printf("qualification succeeded, projecting tuple");

				return ExecProject(node->js.ps.ps_ProjInfo);
			}
			else
				InstrCountFiltered2(node, 1);
		}
		else
			InstrCountFiltered1(node, 1);

		/*
		 * Tuple fails qual, so free per-tuple memory and try again.
		 */
		ResetExprContext(econtext);
	}
}



TupleTableSlot *
ExecActrlJoinInHJMode(PlanState *pstate)
{
	return ExecHashJoinForActrlJoin(pstate);
}
